package com.hzit.order.service.impl;


import com.alibaba.fastjson.JSON;
import com.hzit.cart.api.RemoteCartService;
import com.hzit.cart.api.domain.Order;
import com.hzit.cart.api.domain.OrderItem;
import com.hzit.cart.api.domain.PayLog;
import com.hzit.cart.api.model.ItemVo;
import com.hzit.common.utils.IdWorker;
import com.hzit.order.interceptor.LoginInterceptor;
import com.hzit.order.service.OrderService;

import com.hzit.order.vo.OrderVo;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.exception.TokenMismatchException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * 类名：
 * 作者：WF
 * 功能：
 */
@Service
public class OrderServiceImpl implements OrderService {
	@Autowired
	private RemoteCartService cartService;
	@Autowired
	private IdWorker idWorker;
	@Autowired
	private StringRedisTemplate redisTemplate;

	//1. 引入连接池
	@Autowired
	private ThreadPoolExecutor executor;
	private static final String REDIS_TOKEN_KEY = "hzit:token:";
	private static final String REDIS_PAYLOG_KEY = "hzitmall:paylog:";


	//1. 从redis中查询购物车信息
	@Override
	public Map<String, List<ItemVo>> findCartInfo() {
		return cartService.findCart().getData();
	}

	//2. 保存订单
	@Override
	public void saveOrder(OrderVo order,String login) throws ExecutionException, InterruptedException {
		//2.1 得到购物车信息
		Map<String, List<ItemVo>> cartInfo = findCartInfo();
		//2.2 为了避免重复提交订单
		//2.2.1 从redis中得到token值
		//String token = redisTemplate.opsForValue().get(REDIS_TOKEN_KEY + LoginInterceptor.threadLocal.get().getLogin());
		//2.2.2 为了保证获取,比较,删除三个操作是一个线程来执行,就要保证执行脚本的原子性
		//① 定义lua脚本,保证执行获取,比较,删除三个操作的原子性
		String rs = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
		//② 构造RedisScript脚本对象
		DefaultRedisScript<Long> redisScript = new DefaultRedisScript(rs, Long.class);
		//③ 执行脚本
		Long result = redisTemplate.execute(redisScript, Collections.singletonList(REDIS_TOKEN_KEY + LoginInterceptor.threadLocal.get().getLogin()), order.getToken());
		//2.2.2 与订单中保存的token值进行比较
		if (result == 0L) {  // 说明比较失败
			throw new TokenMismatchException("不能重复提交订单!");
		} else {   // 如果相等说明是第一次提交订单
			//定义异步任务调度集合
			List<CompletableFuture> futureList = new ArrayList<>();
			// 获取当前线程请求头信息(解决异步任务时openFeign调用的丢失请求头问题)   [一定要放在主线程中]
			RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
			//2.3 遍历购物列表
			Set<Long> ids = new HashSet<>();
			List<Double> totalSum = new ArrayList<>();
			for (List<ItemVo> ord : cartInfo.values()) {
				//2.3.1 定义订单对象
				//① 生成订单id
				// long id = idWorker.nextId();

				//场景一：得到随机的订单id值
				CompletableFuture<Long> idFuture = CompletableFuture.supplyAsync(new Supplier<Long>() {
					@Override
					public Long get() {
						// 每一个线程都来共享之前的请求数据
						RequestContextHolder.setRequestAttributes(attributes);
						// 订单编号
						long id = idWorker.nextId();
						ids.add(id);
						//② 设置order号
						order.setOrderId(id);
						//③ 设置创建时间
						order.setCreateTime(LocalDateTime.now());
						order.setUpdateTime(LocalDateTime.now());

						//2.2.4 设置商家id
						order.setSellerId("baidu");
						//2.2.5 设置订单来源类型
						order.setSourceType("2");      // 订单来源：1:app端，2：pc端，3：M端，4：微信端，5：手机qq端
						//2.2.6 设置状态值
						order.setStatus("1");          // 状态：1、未付款，2、已付款，3、未发货，4、已发货，5、交易成功，6、交易关闭,7、待评价
						System.out.println("id = " + id);
						return id;
					}
				}, executor);

				// 当上一个任务完成后接着执行下一个任务，它的特点可以接受返回的值及得到异常
				CompletableFuture<Void> saveOrderFuture = idFuture.thenApply(new Function<Long, Double>() {
					@Override
					public Double apply(Long id) {
						// 每一个线程都来共享之前的请求数据
						RequestContextHolder.setRequestAttributes(attributes);
						ids.add(id);
						//2.3.2 计算订单总金额
						//商家id
						Double sum = 0.0;
						int i = 0;
						for (ItemVo vo : ord) {
							//小计
							double total = vo.getPrice().multiply(new BigDecimal(vo.getBuyNum())).doubleValue();
							//累加小计
							sum += total;
							String sellerId = vo.getSellerId();
							// 生成订单项id
							long itemId = idWorker.nextId();
							//① 定义要保存的订单项
							OrderItem orderItem = new OrderItem();
							orderItem.setGoodsId(vo.getGoodsId());
							orderItem.setItemId(vo.getId());
							orderItem.setNum(vo.getBuyNum());
							orderItem.setSellerId(vo.getSellerId());
							orderItem.setOrderId(id);
							orderItem.setTotalFee(new BigDecimal(total));   // 指定小计
							orderItem.setTitle(vo.getTitle());
							orderItem.setPrice(vo.getPrice());
							orderItem.setPicPath(vo.getImage());
							orderItem.setId(itemId);
							//② 保存订单
							cartService.saveOrderItem(orderItem);

						}

						//2.2.3 设置订单总金额
						order.setPayment(new BigDecimal(sum));
						order.setUserId(login);
						return sum;
					}
				}).thenAccept(new Consumer<Double>() {
					@Override
					public void accept(Double sum) {
						//2.2.7 保存订单
						Order myOrder = new Order();
						BeanUtils.copyProperties(order, myOrder);
						System.out.println("myOrder = " + myOrder);

						cartService.saveOrder(myOrder);

						totalSum.add(myOrder.getPayment().doubleValue());
					}
				});
					 //.exceptionally(): 可以处理异常！

				// todo...
				// 删除购物车
				// 扣减库存
				// 将所有的异步任务添加到futureList集合中
				// futureList.add(idFuture);
				futureList.add(saveOrderFuture);
			}

			// 将异步任务集合转换为异步任务数组
			CompletableFuture[] completableFutures = futureList.toArray(new CompletableFuture[]{});
			// 保证所有的异步任务执行完成
			CompletableFuture.allOf(completableFutures).join();

			//计算所有订单的总金额
			Double sum = 0.0;
			for (Double d : totalSum) {
				sum += d;
			}
			// 将当前的订单信息添加到支付日志中
			Long finalSum = sum.longValue();
			CompletableFuture<Void> savePayLogFuture = CompletableFuture.runAsync(new Runnable() {
				@Override
				public void run() {
					// 每一个线程都来共享之前的请求数据
					RequestContextHolder.setRequestAttributes(attributes);
					PayLog payLog = new PayLog();
					payLog.setOutTradeNo(idWorker.nextId() + "");   // 指定支付日志的编号
					payLog.setCreateTime(LocalDateTime.now());      // 日志创建时间
					payLog.setOrderList(ids.toString().replace("[", "").replace("]", ""));
					payLog.setPayType(order.getPaymentType());
					payLog.setTotalFee(finalSum);  // 订单总金额
					payLog.setTradeState("0");                       // 0：未支付 1： 己支付
					payLog.setUserId(login);
					System.out.println("payLog = " + payLog);
					cartService.savePayLog(payLog);

					// 将支付日志添加到redis中
					redisTemplate.opsForValue().set( REDIS_PAYLOG_KEY + login, JSON.toJSONString(payLog),30,TimeUnit.DAYS);

				}
			}, executor);

			CompletableFuture.allOf(savePayLogFuture).join();

		}

	}

	//3. 从redis中取得支付日志信息
	@Override
	public PayLog getPayLogFromRedis(String login) {
		String s = redisTemplate.opsForValue().get(REDIS_PAYLOG_KEY + login);
		return JSON.parseObject(s,PayLog.class);
	}

	//4. 根据登录名查询用户的订单列表
	@Override
	public List<Order> getOrderList() {
		//4.1 得到登录名
		String login = LoginInterceptor.threadLocal.get().getLogin();
		//4.2 远程查询
		R<List<Order>> r = cartService.getOrderList(login);

		return r.getData();
	}


}
